• bash shell会将一些称为位置参数的特殊变量分配给输入到命令行中的所有参数,其中$0是程序名,$1是第一个参数,$2是第二个参数…

    1
    2
    3
    4
    5
    6
    7
    #!/bin/bash
    factorial=1
    for (( number = 1; number <= $1 ; number++ ))
    do
    factorial=[ factorial * $number ]
    done
    echo The factorial of 1 is factorial
    1
    2
    $ ./test1.sh 5
    The factorial of 5 is 120

    需要注意的是factorial=$[ $factorial * $number ]这一行等号左右不可以有空格。

  • 在将文本字符串作为参数传递时,若碰到有空格的文本字符串,需要用引号将文本字符串包起来
  • 读取脚本名:可以用$0参数获取shell在命令行启动的脚本名,例如bash test.sh,那么在shell脚本中使用$0获取到的脚本名为test.sh,但是有一个问题就是使用./test.sh运行程序时,命令会和脚本名混在一起,例如执行./test.sh,那么在脚本中$0获取到的脚本名为./test.sh,另外一个问题就是当传给$0变量的实际字符串不仅仅是脚本名,而是完整的脚本路径时,变量$0就是使用整个路径,例如bash /home/gtp/test.sh,那么在脚本中获取的值就为/home/gtp/test.sh,有个命令可以帮我们提取出脚本名称,basename命令会返回不包含路径的脚本名,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #!/bin/bash
    name=(basename 0)
    if [ $name = "addem" ]
    then
    total=$[ $1 + $2 ]
    elif [ $name = "multem" ]
    then
    total=$[ $1 * $2 ]
    fi
  • ​在shell脚本中使用命令行参数如果脚本不加参数运行会报错,所以在脚本中使用命令行参数时一定要先判断:

    1
    2
    3
    4
    5
    6
    7
    #!/bin/bash
    if [ -n "$1" ]
    then
    echo Hello $1, glad to meet you.
    else
    echo "Sorry, you did not identify yourself"
    fi
  • 特殊变量$#含有脚本运行时携带的命令行参数的个数,可以在脚本中任何地方使用这个特殊变量,就跟普通变量一样,我们通过如下形式获取到最后一个命令行参数:

    1
    2
    3
    4
    5
    6
    #!/bin/bash
    params=$#
    echo
    echo The last parameter is $params
    echo The last parameter is ${!#}
    echo
    1
    2
    3
    $ bash test.sh 1 2 3 4 5
    The last parameter is 5
    The last parameter is 5
    1
    2
    3
    $ bash test.sh
    The last parameter is 0
    The last parameter is test.sh
  • $*$@变量可以轻松访问所有的命令行参数,这两个变量都能够在单个变量上存储所有的命令行参数,$*变量会将命令行上提供的所有参数当作一个单词保存,这个单词包含了命令行上出现的每一个参数值,$*变量会将这些参数视为一个整体,而不是多个个体;而$@变量会将命令行上提供的所有参数当作同一字符串中的多个独立的单词,这样就能遍历所有的参数值,得到每一个参数值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #!/bin/bash
    echo
    count=1
    for param in "$*"
    do
    echo "$* Parameter #count = param"
    count=[ count + 1 ]
    done
    echo
    count=1
    for param in "$@"
    do
    echo "$@ Paramter #count = param"
    count=[ count + 1]
    done
    1
    2
    3
    4
    5
    6
    $ ./test.sh rich barbara katie jessica
    $* Parameter #1 = rich barbara katie jessica
    $@ Parameter #1 = rich
    $@ Parameter #2 = barbara
    $@ Parameter #3 = katie
    $@ Parameter #4 = jessica
  • 移动变量:shift命令能够用来操作命令行参数,shift命令会根据它们的相对位置来移动命令行参数,在使用shift命令时,默认情况下它会将每个参数变量向左移动一个位置,所以,变量$3的值会移到$2中,变量$2的值会移到$1中,而变量$1的值则会被删除(注意,变量$0的值,也就是程序名,不会改变)。这是遍历命令行参数的另一种方法,你可以只操作第一个参数,移动参数,然后继续操作第一个参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #!/bin/bash
    echo
    count=1
    while [ -n "$1" ]
    do
    echo "Parameter #$count = $1"
    count=$[ $count + 1 ]
    shift
    done
    1
    2
    3
    4
    5
    6
    $
    $ ./test13.sh rich barbara katie jessica
    Parameter #1 = rich
    Parameter #2 = barbara
    Parameter #3 = katie
    Parameter #4 = jessica

    使用shift命令需要注意的是如果某个参数被移出,它的值就被丢弃了,无法再恢复。

  • 你可以使用shift命令来依次处理脚本程序携带的命令行参数,你也可以用同样的方法来处理命令行选项,在提取每个单独参数时,用case语句来判断某个参数是否为选项:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #!/bin/bash
    echo
    while [ -n "$1" ]
    do
    case "$1" in
    -a) echo "Found the -a option" ;;
    -b) echo "Found the -a option" ;;
    -c) echo "Found the -c option" ;;
    *) echo "$1 is not an option" ;;
    esac
    shift
    done
    1
    2
    3
    4
    5
    $ ./test.sh -a -b -c -d
    Found the -a option
    Found the -b option
    Found the -c option
    -d is not an option
  • Linux中使用--特殊字符将选项和参数分开,shell会用双破折线来表明选项列表结束,在双破折线之后,脚本就可以放心的将剩下的命令行参数当作参数,而不是选项来处理了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #!/bin/bash
    echo
    while [ -n "$1" ]
    do
    case "$1" in
    -a) echo "Found the -a option" ;;
    -b) echo "Found the -b option" ;;
    -c) echo "Found the -c option" ;;
    --) shift
    break ;;
    *) echo "$1 is not an option" ;;
    esac
    shift
    done
    #
    count=1
    for param in $@
    do
    echo "Parameter #$count: $param"
    count=$[ $count + 1 ]
    done
    1
    2
    3
    4
    5
    6
    7
    $ ./test16.sh -c -a -b -- test1 test2 test3
    Found the -c option
    Found the -a option
    Found the -b option
    Parameter #1: test1
    Parameter #2: test2
    Parameter #3: test3
  • 处理带值的选项:有些选项会带上一个额外的参数值,在这种情况下,命令行会类似于下面这种形式:./test.sh -a -b test1 -c -d test2,当命令行选项要求额外的参数时,脚本必须能够检测到并正确处理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #!/bin/bash
    echo
    while [ -n "$1" ]
    do
    case "$1" in
    -a) echo "Found the -a option"
    -b) param="$2"
    echo "Found the -b option, with parameter value $param"
    shift ;;
    -c) echo "Found the -c option" ;;
    --) shift
    break ;;
    *) echo "$1 is not an option"
    esac
    shift
    done
    #
    count=1
    for param in $@
    do
    echo "Parameter #$count: $param"
    count=$[ $count + 1 ]
    done
    1
    2
    3
    4
    $ ./test.sh -a -b test1 -d
    Found the -a option
    Found the -b option, with parameter value test1
    -d is not an option
  • getopt命令是一个处理命令行选项和参数的命令,它能够识别命令行参数,getopt命令可以接受一系列任意形式的命令行选项和参数,并自动将它们转换成适当的形式,它的命令形式如下:getopt optstring parameters

    optstring定义了命令行有效的选项字母,还定义了哪些选项字母需要参数值,在optstring中列出你要在脚本中用到的每个命令行选项字母,然后在每个需要参数值的选项字母后加一个冒号,getopt命令会基于你定义的optstring解析提供的参数

    1
    2
    $ getopt ab:cd -a -b test1 -cd test2 test3
    a -b test1 -c -d -- test2 test3

    optstring定义了四个有效选项字母,a、b、c和d,冒号被放在了b后面,表示b选项需要一个参数值,当getopt命令运行时,它会检查提供的参数列表(-a -b test1 -cd test2 test3),并基于提供的optstring进行解析,它会自动将-cd选项分为两个独立的选项,并插入双破折线来分隔行中的额外参数。

  • 可以在脚本中使用getopt来格式化脚本所携带的任何命令行选项和参数,方法是用getopt命令生成的格式化后的版本来替换已有的命令行选项和参数,这可以通过set命令做到,set命令的选项之一是双破折线--,它会将命令行参数替换成set命令的命令行值:

    1
    set -- $(getopt -q ab:cd "$@")

    -q选项表示如果指定了一个不在optstring中的选项,默认情况下,getopt命令会产生一条错误信息,指定了-q选项后就可以忽略这条错误信息。

    现在原始的命令行参数变量的值会被getopt命令的输出替换:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #!/bin/bash
    #Extract command line options & values with getopt
    #
    set -- $(getopt -q ab:cd "$@")
    #
    echo
    while [ -n "$1" ]
    do
    case "$1" in
    -a) echo "Found the -a option" ;;
    -b) param="$2"
    echo "Found the -b option, with parameter value $param"
    shift ;;
    -c) echo "Found the -c option" ;;
    --) shift
    break ;;
    *) echo "$1 is not an option";;
    esac
    shift
    done
    #
    count=1
    for param in "$@"
    do
    echo "Parameter #$count: $param"
    count=$[ $count + 1 ]
    done
    1
    2
    3
    $ ./test.sh -ac
    Found the -a option
    Found the -c option
    1
    2
    3
    4
    5
    6
    7
    $ ./test.sh -a -b test1 -cd test2 test3 test4
    Found the -a option
    Found the -b option, with parameter value 'test1'
    Found the -c option
    Parameter #1: 'test2'
    Parameter #2: 'test3'
    Parameter #3: 'test4'
  • getopt存在的一个问题就是当处理带空格和引号的参数值时,它会将空格当作参数分隔符,而不是根据双引号将二者当作一个参数,例如:

    1
    ./test.sh -a -b test1 -cd "test2 test3" test4
    1
    2
    3
    4
    5
    6
    Found the -a option
    Found the -b option, with parameter value 'test1'
    Found the -c option
    Parameter #1: 'test2
    Parameter #2: test3'
    Parameter #3: 'test4'
  • getopts一次只处理命令行上检测到的一个参数,处理完所有的参数后,它会退出并返回一个大于0的退出状态码,这让它非常适合用解析命令行所有参数的循环中: getopts optstring variable

    有效的选项字母都会列在optstring中,如果选项字母要求有个参数值,就加一个冒号,要去掉错误信息的话,可以在optstring之前加一个冒号,getopts命令会将当前参数保存在命令行中定义的variable中。

    getopts命令会用到两个环境变量,如果选项要跟一个参数值,OPTARG环境变量就会保存这个值,OPTIND环境变量保存了参数列表中getopts正在处理的参数位置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #!/bin/bash
    echo
    while getopts :ab:c opt
    do
    case "$opt" in
    a) echo "Found the -a option" ;;
    b) echo "Found the -b option, with value $OPTARG" ;;
    a) echo "Found the -c option" ;;
    *) echo "Unknown option: $opt" ;;
    esac
    done
    1
    2
    3
    4
    $ ./test19.sh -ab test1 -c
    Found the -a option
    Found the -b option, with value test1
    Found the -c option

    需要注意的是,getopts命令解析命令行选项时会移除开头的破折线,所以在case定义中不用单破折线。

  • getopts可以在参数中包含空格:

    1
    $ ./test.sh -b "test1 test2" -a
    1
    2
    Found the -b option, with value test1 test2
    Found the -a option
  • 选项字母和参数值放在一起使用,而不用加空格

    1
    $ ./test.sh -abtest1
    1
    2
    Found the -a option
    Found the -b option, with value test1
  • getopts还能够将命令行上找到的所有未定义的选项统一输出成问号

    1
    $ ./test19.sh -d
    1
    Unknown option: ?
  • 在getopts处理每个选项时,它会将OPTIND环境变量值增一。在getopts完成处理时,你可以使用shift命令和OPTIND值来移动参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #!/bin/bash
    echo
    while getopts :ab:cd opt
    do
    case "$opt" in
    a) echo "Found the -a option" ;;
    b) echo "Found the -b option, with value $OPTARG;;
    c) echo "Found the -c option" ;;
    d) echo "Found the -d option" ;;
    *) echo "Unknown option: $opt" ;;
    esac
    done
    #
    shift $[ OPTIND - 1 ]
    #
    echo
    count=1
    for param in "$@"
    do
    echo "Parameter $count: $param"
    count=$[ $count + 1 ]
    done
    1
    2
    3
    4
    5
    6
    7
    8
    $ ./test.sh -a -b test1 -d test2 test3 test4
    Found the -a option
    Found the -b option, with value test1
    Found the -d option
    Parameter 1: test2
    Parameter 2: test3
    Parameter 3: test4

    注意:OPTIND总是存储下一个要处理的元素位置,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #!/bin/bash
    echo
    while getopts :ab:cd opt
    do
    case "$opt" in
    a) echo "a = $OPTIND" ;;
    b) echo "b = $OPTIND" ;;
    c) echo "c = $OPTIND" ;;
    d) echo "d = $OPTIND" ;;
    esac
    done
    1
    2
    3
    4
    5
    $ ./test.sh -a -b test1 -c -d
    a = 2
    b = 4
    c = 5
    d = 6

    之所以会有上述输出是因为当处理命令行参数-a时,下一个要处理的参数是-b,而-b参数所在的位置为2,所以a = 2,当处理-b时,下一个参数是test1,但test1不是命令行选项,所以跳过,因此下一个要处理的参数是-c,而-c参数所在的位置为4,后面几个输出的道理是一样的。

    我们看另外一种情况:

    1
    2
    3
    4
    5
    $ ./test.sh -ab test1 -c -d
    a = 1
    b = 3
    c = 4
    d = 5

    之所以会有上述输出是因为当成处理命令行参数-a时,下一个要处理的参数是b,依然在处理第一个元素,因此a = 1,当处理b时,下一个参数是test1,但test1不是命令行选项,所以跳过,一次下一个要处理的参数是-c,而-c参数所在的位置为3,后面几个输出的道理是一样的。

  • read命令从标准输入(键盘)或另一个文件描述符中接受输入,在受到输入后,read命令会将数据放进一个变量:

    1
    2
    3
    4
    #!/bin/bash
    echo -n "Enter your name: "
    read name
    echo "Hello $name, welcome"
    1
    2
    3
    $./test.sh
    Enter your name: GTP
    Hello GTP, welcome

    echo命令后面的-n选项表示不会在字符串末尾输出换行符,允许脚本用户紧跟其后输入数据,而不是下一行。

  • read命令包含了-p选项,允许直接在read命令行指定提示符:

    1
    2
    3
    4
    #!/bin/bash
    read -p "Please enter your age: " age
    days=$[ age * 365 ]
    echo "That makes you over $days days old!"
    1
    2
    3
    $ ./test.sh
    Please enter your age: 10
    That makes you over 3650 days old!
  • read命令会将姓和名保存在同一个变量中,read命令会将提示符后输入的所有数据分配给单个变量,要么你指定多个变量,输入的每个数据值都会分配给变量列表中的下一个变量,如果变量数量不够,剩下的数据就全部分配给最后一个变量:

    1
    2
    3
    #!/bin/bash
    read -p "Enter your name: " first last
    echo "Checking data for last, first"
    1
    2
    3
    $ ./test.sh
    Enter your name: Rich Blum
    Checking data for Blum, Rich
  • 也可以在read命令中不指定变量,这样read命令会将它收到的任何数据都放进特殊环境变量REPLY中

    1
    2
    3
    4
    #!/bin/bash
    read -p "Enter your name: "
    echo
    echo Hello $REPLY, welcome!
    1
    2
    3
    $./test.sh
    Enter your name: GTP
    Hello GTP, welcome!
  • read命令可以通过-t选项指定一个计时器,指定了read命令等待输入的秒数,当计时器过期后,read命令会返回一个非零退出状态码:

    1
    2
    3
    4
    5
    6
    7
    8
    #!/bin/bash
    if read -t 5 -p "Please enter your name: " name
    then
    echo "Hello $name, welcome!"
    else
    echo
    echo "Sorry, too slow!"
    fi
  • read命令还可以统计输入的字符数,当输入的字符数达到预设的字符数时,就自动退出,将输入的数据赋给变量:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #!/bin/bsh
    read -n1 -p "Do you want to continue [Y/N]? " answer
    case $answer in
    Y | y) echo
    echo "fine, continue on..." ;;
    N | n) echo
    echo "OK, goodbye"
    exit ;;
    esac
    echo "This is the end of the script"

    -n1选项告诉read命令在接受单个字符后退出,只要按下单个字符回答后,read命令就会接受输入并将它传给变量,无需按回车键。

  • read命令的-s选项可以避免在read命令中输入的数据显示在显示器上(实际上数据会被显示,只是read命令会将文本颜色设成跟背景色一样):

    1
    2
    3
    4
    #!/bin/bash
    read -s -p "Enter your password: " pass
    echo
    echo "Is your password really $pass? "
    1
    2
    3
    $ ./test.sh
    Enter your password:
    Is your password really T3stlng?

    这样输入提示符输入的数据不会出现在屏幕上,但会赋给变量。

  • read命令可以读取文件中保存的数据,每次调用read命令, 会从文件中读取一行文本,当文件中再没有内容时,read命令会退出并返回非零退出状态码,最常见的方法是对文件使用cat命令,将结果通过管道直接传给read命令的while命令:

    1
    2
    3
    4
    5
    6
    7
    8
    #!/bin/bash
    count=1
    cat test | while read line
    do
    echo "Line $count: $line"
    count=$[ $count + 1 ]
    done
    echo "Finished procession the file"